/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* 
 * gximserver.c
 * Copyright (C) 2008 Akira TAGOH
 * 
 * Authors:
 *   Akira TAGOH  <akira@tagoh.org>
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif
#include <glib/gi18n-lib.h>
#include "gximattr.h"
#include "gximmarshal.h"
#include "gximmessage.h"
#include "gximmisc.h"
#include "gximprotocol.h"
#include "gximsrvconn.h"
#include "gximtransport.h"
#include "gximserver.h"

#define G_XIM_SERVER_GET_PRIVATE(_o_)		(G_TYPE_INSTANCE_GET_PRIVATE ((_o_), G_TYPE_XIM_SERVER, GXimServerPrivate))

enum {
	PROP_0,
	PROP_DEFAULT_IM_ATTRS,
	PROP_DEFAULT_IC_ATTRS,
	LAST_PROP
};
enum {
	SIGNAL_0,
	LAST_SIGNAL
};

struct _GXimServerPrivate {
	GSList     *on_trigger_keys;
	GSList     *off_trigger_keys;
	gchar      *default_im_attrs;
	gchar      *default_ic_attrs;
	GHashTable *conn_table;
	guint       latest_imid;
};

static gboolean g_xim_server_real_XIM_EXT_SET_EVENT_MASK(GXimProtocolClosure  *closure,
							 GXimProtocol         *proto,
							 GDataInputStream     *stream,
							 GError              **error,
							 gpointer              data);
static gboolean g_xim_server_real_xim_open                (GXimProtocol     *proto,
                                                           const GXimStr    *locale,
                                                           gpointer          data);
static gboolean g_xim_server_real_xim_close               (GXimProtocol     *proto,
                                                           guint16           imid,
                                                           gpointer          data);
static gboolean g_xim_server_real_xim_encoding_negotiation(GXimProtocol     *proto,
                                                           guint             imid,
                                                           const GSList     *encodings,
                                                           const GSList     *details,
                                                           gpointer          data);
static gboolean g_xim_server_real_xim_get_im_values       (GXimProtocol     *proto,
                                                           guint16           imid,
                                                           const GSList     *attributes,
                                                           gpointer          data);

//static guint signals[LAST_SIGNAL] = { 0 };


G_DEFINE_TYPE (GXimServer, g_xim_server, G_TYPE_XIM_SRV_TMPL);

/*
 * Private functions
 */
static void
g_xim_server_real_set_property(GObject      *object,
			       guint         prop_id,
			       const GValue *value,
			       GParamSpec   *pspec)
{
	GXimServerPrivate *priv = G_XIM_SERVER_GET_PRIVATE (object);
	gchar *p;

	switch (prop_id) {
	    case PROP_DEFAULT_IM_ATTRS:
		    p = g_strdup(g_value_get_string(value));
		    if (p == NULL) {
			    GValue v = { 0, };

			    g_value_init(&v, G_TYPE_STRING);
			    g_param_value_set_default(pspec, &v);
			    p = g_strdup(g_value_get_string(&v));
			    g_value_unset(&v);
		    }
		    g_free(priv->default_im_attrs);
		    priv->default_im_attrs = p;
		    break;
	    case PROP_DEFAULT_IC_ATTRS:
		    p = g_strdup(g_value_get_string(value));
		    if (p == NULL) {
			    GValue v = { 0, };

			    g_value_init(&v, G_TYPE_STRING);
			    g_param_value_set_default(pspec, &v);
			    p = g_strdup(g_value_get_string(&v));
			    g_value_unset(&v);
		    }
		    g_free(priv->default_ic_attrs);
		    priv->default_ic_attrs = p;
		    break;
	    default:
		    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		    break;
	}
}

static void
g_xim_server_real_get_property(GObject    *object,
			       guint       prop_id,
			       GValue     *value,
			       GParamSpec *pspec)
{
	GXimServerPrivate *priv = G_XIM_SERVER_GET_PRIVATE (object);

	switch (prop_id) {
	    case PROP_DEFAULT_IM_ATTRS:
		    g_value_set_string(value, priv->default_im_attrs);
		    break;
	    case PROP_DEFAULT_IC_ATTRS:
		    g_value_set_string(value, priv->default_ic_attrs);
		    break;
	    default:
		    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		    break;
	}
}

static void
g_xim_server_real_finalize(GObject *object)
{
	GXimServerPrivate *priv = G_XIM_SERVER_GET_PRIVATE (object);

	g_slist_free(priv->on_trigger_keys);
	g_slist_free(priv->off_trigger_keys);
	g_hash_table_destroy(priv->conn_table);
	g_free(priv->default_im_attrs);
	g_free(priv->default_ic_attrs);

	if (G_OBJECT_CLASS (g_xim_server_parent_class)->finalize)
		(* G_OBJECT_CLASS (g_xim_server_parent_class)->finalize) (object);
}

static void
g_xim_server_real_setup_connection(GXimCore       *core,
				   GXimConnection *conn)
{
	GXimProtocolClosure *c;

	/* register the extensions */
#define G_XIM_EXT_SET_EVENT_MASK 0x30
	c = g_xim_protocol_closure_new(G_XIM_EXT_SET_EVENT_MASK, 0,
				       "XIM_EXT_SET_EVENT_MASK", TRUE);
	G_XIM_CHECK_ALLOC_WITH_NO_RET (c);

	g_xim_protocol_closure_set_marshal(c,
					   g_xim_server_real_XIM_EXT_SET_EVENT_MASK,
					   core);
	/* XXX: add signal */
	g_xim_protocol_add_protocol(G_XIM_PROTOCOL (conn), c);
}

static void
g_xim_server_class_init(GXimServerClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	GXimCoreClass *core_class = G_XIM_CORE_CLASS (klass);

	g_type_class_add_private(klass, sizeof (GXimServerPrivate));

	object_class->set_property = g_xim_server_real_set_property;
	object_class->get_property = g_xim_server_real_get_property;
	object_class->finalize     = g_xim_server_real_finalize;

	core_class->setup_connection = g_xim_server_real_setup_connection;

	/* properties */
	g_object_class_install_property(object_class, PROP_DEFAULT_IM_ATTRS,
					g_param_spec_string("default_im_attrs",
							    _("Default IM Attributes"),
							    _("Default IM Attributes"),
							    XNQueryInputStyle,
							    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
	g_object_class_install_property(object_class, PROP_DEFAULT_IC_ATTRS,
					g_param_spec_string("default_ic_attrs",
							    _("Default IC Attributes"),
							    _("Default IC Attributes"),
							    XNInputStyle "," \
							    XNClientWindow "," \
							    XNFocusWindow "," \
							    XNPreeditAttributes "," \
							    XNForeground "," \
							    XNBackground "," \
							    XNSpotLocation "," \
							    XNFontSet "," \
							    XNArea "," \
							    XNLineSpace "," \
							    XNStatusAttributes "," \
							    XNAreaNeeded "," \
							    XNColormap "," \
							    XNStdColormap "," \
							    XNBackgroundPixmap "," \
							    XNCursor "," \
							    XNFilterEvents "," \
							    XNSeparatorofNestedList,
							    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

	/* signals */
}

static void
g_xim_server_init(GXimServer *server)
{
	GXimServerPrivate *priv = G_XIM_SERVER_GET_PRIVATE (server);
	GXimLazySignalConnector sigs[] = {
		{"XIM_OPEN", G_CALLBACK (g_xim_server_real_xim_open), server},
		{"XIM_CLOSE", G_CALLBACK (g_xim_server_real_xim_close), server},
		{"XIM_ENCODING_NEGOTIATION", G_CALLBACK (g_xim_server_real_xim_encoding_negotiation), server},
		{"XIM_GET_IM_VALUES", G_CALLBACK (g_xim_server_real_xim_get_im_values), server},
		{NULL, NULL, NULL}
	};

	g_object_set(server, "proto_signals", sigs, NULL);

	priv->on_trigger_keys = NULL;
	priv->off_trigger_keys = NULL;
	priv->default_im_attrs = NULL;
	priv->default_ic_attrs = NULL;
	priv->conn_table = g_hash_table_new(g_direct_hash, g_direct_equal);
	priv->latest_imid = 1;
}

static guint
g_xim_server_find_imid(GXimServer *server)
{
	GXimServerPrivate *priv = G_XIM_SERVER_GET_PRIVATE (server);
	gboolean overflowed = FALSE;
	guint begin_id = priv->latest_imid;

	while (1) {
		if (g_hash_table_lookup(priv->conn_table, GUINT_TO_POINTER (priv->latest_imid)) == NULL)
			break;
		if (overflowed && priv->latest_imid >= begin_id)
			return 0;
		priv->latest_imid++;
		if (priv->latest_imid == 0) {
			overflowed = TRUE;
			priv->latest_imid = 1;
		}
	}

	return priv->latest_imid;
}

static gboolean
g_xim_server_real_XIM_EXT_SET_EVENT_MASK(GXimProtocolClosure  *closure,
					 GXimProtocol         *proto,
					 GDataInputStream     *stream,
					 GError              **error,
					 gpointer              data)
{
	g_warning("Not yet implemented.");

	return FALSE;
}

/* protocol callbacks */
static gboolean
g_xim_server_real_xim_open(GXimProtocol  *proto,
			   const GXimStr *locale,
			   gpointer       data)
{
	GXimAttr *attr;
	GXimIMAttr *imattr = NULL;
	GXimICAttr *icattr = NULL;
	guint imid = 0;
	GXimServer *server = G_XIM_SERVER (data);
	GXimServerPrivate *priv = G_XIM_SERVER_GET_PRIVATE (server);
	GXimConnection *conn = G_XIM_CONNECTION (proto);
	GdkNativeWindow client_window = g_xim_transport_get_client_window(G_XIM_TRANSPORT (proto));
	GSList *l, *imlist = NULL, *iclist = NULL, *list;
	GXimRawAttr *raw;

	g_return_val_if_fail (conn->imid == 0, FALSE);

	imid = g_xim_server_find_imid(server);
	if (imid > 0) {
		g_xim_message_debug(G_XIM_PROTOCOL_GET_IFACE (proto)->message, "proto/event",
				    "XIM_OPEN[%s] from %p - imid: %d",
				    g_xim_str_get_string(locale),
				    G_XIM_NATIVE_WINDOW_TO_POINTER (client_window),
				    imid);
		if (priv->on_trigger_keys && priv->off_trigger_keys) {
			/* This session works as the Dynamic Event Flow model */
			g_xim_server_connection_register_triggerkeys(G_XIM_SERVER_CONNECTION (proto),
								     imid,
								     priv->on_trigger_keys,
								     priv->off_trigger_keys);
		}
		imattr = g_xim_im_attr_new(priv->default_im_attrs);
		icattr = g_xim_ic_attr_new(priv->default_ic_attrs);
		conn->imid = imid;
		conn->imattr = imattr;
		conn->default_icattr = icattr;

		attr = G_XIM_ATTR (imattr);
		list = g_xim_attr_get_supported_attributes(attr);
		for (l = list; l != NULL; l = g_slist_next(l)) {
			if (l->data && g_xim_attr_attribute_is_enabled(attr, l->data)) {
				guint id;
				GType gtype;
				GXimValueType vtype;
				GString *s;

				id = g_xim_attr_get_attribute_id(attr, l->data);
				gtype = g_xim_attr_get_gtype_by_name(attr, l->data);
				vtype = g_xim_gtype_to_value_type(gtype);
				if (vtype == G_XIM_TYPE_INVALID) {
					g_xim_message_bug(G_XIM_CORE (server)->message,
							  "Unable to compose a XIMATTR for %s",
							  (gchar *)l->data);
					continue;
				}
				s = g_string_new(l->data);
				G_XIM_CHECK_ALLOC (s, FALSE);

				raw = g_xim_raw_attr_new_with_value(id, s, vtype);
				G_XIM_CHECK_ALLOC (raw, FALSE);

				imlist = g_slist_append(imlist, raw);
			}
			g_free(l->data);
		}
		g_slist_free(list);

		attr = G_XIM_ATTR (icattr);
		list = g_xim_attr_get_supported_attributes(attr);
		for (l = list; l != NULL; l = g_slist_next(l)) {
			if (l->data && g_xim_attr_attribute_is_enabled(attr, l->data)) {
				guint id;
				GType gtype;
				GXimValueType vtype;
				GString *s;

				id = g_xim_attr_get_attribute_id(attr, l->data);
				gtype = g_xim_attr_get_gtype_by_name(attr, l->data);
				vtype = g_xim_gtype_to_value_type(gtype);
				if (vtype == G_XIM_TYPE_INVALID) {
					g_xim_message_bug(G_XIM_CORE (server)->message,
							  "Unable to compose a XICATTR for %s",
							  (gchar *)l->data);
					continue;
				}
				s = g_string_new(l->data);
				G_XIM_CHECK_ALLOC (s, FALSE);

				raw = g_xim_raw_attr_new_with_value(id, s, vtype);
				G_XIM_CHECK_ALLOC (raw, FALSE);

				iclist = g_slist_append(iclist, raw);
			}
			g_free(l->data);
		}
		g_slist_free(list);

		g_xim_server_connection_open_reply(G_XIM_SERVER_CONNECTION (proto),
						   imid, imlist, iclist);

		g_slist_foreach(imlist, (GFunc)g_xim_raw_attr_free, NULL);
		g_slist_foreach(iclist, (GFunc)g_xim_raw_attr_free, NULL);
		g_slist_free(imlist);
		g_slist_free(iclist);
	} else {
		g_xim_message_warning(G_XIM_CORE (server)->message,
				      "No imid available for %p.",
				      G_XIM_NATIVE_WINDOW_TO_POINTER (client_window));
	}

	return imid > 0;
}

static gboolean
g_xim_server_real_xim_close(GXimProtocol  *proto,
			    guint16        imid,
			    gpointer       data)
{
	GXimServer *server = G_XIM_SERVER (data);
	GXimServerPrivate *priv = G_XIM_SERVER_GET_PRIVATE (server);
	GXimConnection *conn;
	GdkNativeWindow client_window = g_xim_transport_get_client_window(G_XIM_TRANSPORT (proto));

	if ((conn = g_hash_table_lookup(priv->conn_table, GUINT_TO_POINTER (imid))) == NULL) {
		g_xim_message_warning(G_XIM_CORE (server)->message,
				      "Invalid imid `%d' from %p to close the connection.",
				      imid,
				      G_XIM_NATIVE_WINDOW_TO_POINTER (client_window));
		return FALSE;
	}
	g_xim_message_debug(G_XIM_PROTOCOL_GET_IFACE (proto)->message, "proto/event",
			    "XIM_CLOSE[imid:%d] from %p",
			    imid,
			    G_XIM_NATIVE_WINDOW_TO_POINTER (client_window));

	g_xim_server_connection_close_reply(G_XIM_SERVER_CONNECTION (proto),
					    imid);
	g_hash_table_remove(priv->conn_table, GUINT_TO_POINTER (imid));
	g_object_unref(conn);

	return TRUE;
}

static gint
_cmp_str(gconstpointer a,
	 gconstpointer b)
{
	const GXimStr *ss = a;
	const gchar *s = g_xim_str_get_string(ss);

	return g_ascii_strcasecmp(s, b);
}

static gboolean
g_xim_server_real_xim_encoding_negotiation(GXimProtocol  *proto,
					   guint          imid,
					   const GSList  *encodings,
					   const GSList  *details,
					   gpointer       data)
{
	GSList *l;

	/* XXX: need to get rid of hard-coded encoding support */
	l = g_slist_find_custom((GSList *)encodings, "UTF-8", _cmp_str);
	if (l == NULL)
		return FALSE;

	return g_xim_server_connection_encoding_negotiation_reply(G_XIM_SERVER_CONNECTION (proto),
								  imid,
								  0,
								  g_slist_position((GSList *)encodings, l));
}

static gboolean
g_xim_server_real_xim_get_im_values(GXimProtocol  *proto,
				    guint16        imid,
				    const GSList  *attributes,
				    gpointer       data)
{
	GXimServer *server = G_XIM_SERVER (data);
	GXimServerPrivate *priv = G_XIM_SERVER_GET_PRIVATE (server);
	GXimConnection *conn;
	GXimIMAttr *attr = NULL;
	const GSList *l;
	GSList *list = NULL;
	GXimAttribute *a;
	GType gtype;
	GXimValueType vtype;
	gpointer value;
	GdkNativeWindow client_window = g_xim_transport_get_client_window(G_XIM_TRANSPORT (proto));
	gboolean retval;

	conn = g_hash_table_lookup(priv->conn_table, GUINT_TO_POINTER (imid));
	if (!conn)
		return FALSE;

	attr = conn->imattr;
	for (l = attributes; l != NULL; l = g_slist_next(l)) {
		gtype = g_xim_attr_get_gtype_by_id(G_XIM_ATTR (attr),
						   GPOINTER_TO_INT (l->data));
		vtype = g_xim_gtype_to_value_type(gtype);
		if (vtype == G_XIM_TYPE_INVALID) {
			g_xim_message_warning(G_XIM_CORE (server)->message,
					      "Invalid attribute ID %d received from %p",
					      GPOINTER_TO_INT (l->data),
					      G_XIM_NATIVE_WINDOW_TO_POINTER (client_window));
			continue;
		}
		value = g_xim_attr_get_value_by_id(G_XIM_ATTR (attr),
						   GPOINTER_TO_INT (l->data));
		a = g_xim_attribute_new_with_value(GPOINTER_TO_INT (l->data), vtype, value);
		G_XIM_CHECK_ALLOC (a, FALSE);

		list = g_slist_append(list, a);
	}
	retval = g_xim_server_connection_get_im_values_reply(G_XIM_SERVER_CONNECTION (conn), imid, list);

	g_slist_foreach(list, (GFunc)g_xim_attribute_free, NULL);
	g_slist_free(list);

	return retval;
}

/*
 * Public functions
 */
GXimServer *
g_xim_server_new(GdkDisplay  *dpy,
		 const gchar *server_name,
		 const gchar *default_im_attrs,
		 const gchar *default_ic_attrs)
{
	GXimServer *retval;

	g_return_val_if_fail (GDK_IS_DISPLAY (dpy), NULL);
	g_return_val_if_fail (server_name != NULL, NULL);

	retval = G_XIM_SERVER (g_object_new(G_TYPE_XIM_SERVER,
					    "display", dpy,
					    "server_name", server_name,
					    "connection_gtype", G_TYPE_XIM_SERVER_CONNECTION,
					    "default_im_attrs", default_im_attrs,
					    "default_ic_attrs", default_ic_attrs,
					    NULL));

	return retval;
}

GQuark
g_xim_server_get_error_quark(void)
{
	static GQuark quark = 0;

	if (!quark)
		quark = g_quark_from_static_string("g-xim-server-error");

	return quark;
}

gboolean
g_xim_server_is_running(GXimServer  *server,
			GError     **error)
{
	/* this function would be mainly used to bring the server up.
	 * so returning TRUE would be reasonable for any unexpected errors
	 * to avoid next step.
	 */
	g_return_val_if_fail (G_IS_XIM_SERVER (server), TRUE);

	return g_xim_srv_tmpl_is_running(G_XIM_SRV_TMPL (server), error);
}

gboolean
g_xim_server_take_ownership(GXimServer  *server,
			    gboolean     force,
			    GError     **error)
{
	g_return_val_if_fail (G_IS_XIM_SERVER (server), FALSE);

	return g_xim_srv_tmpl_take_ownership(G_XIM_SRV_TMPL (server),
					     force,
					     error);
}

#if 0
void
g_xim_server_add_trigger_key(GXimServer     *server,
			     GXimTriggerKey *key,
			     gboolean        for_on_state)
{
	GXimServerPrivate *priv;

	g_return_if_fail (G_IS_XIM_SERVER (server));
	g_return_if_fail (key != NULL);

	priv = G_XIM_SERVER_GET_PRIVATE (server);

	if (!g_xim_server_lookup_trigger_key(server, key, for_on_state)) {
		if (for_on_state)
			g_slist_append(priv->on_trigger_keys, key);
		else
			g_slist_append(priv->off_trigger_keys, key);
	}
}

void
g_xim_server_remove_trigger_key(GXimServer     *server,
				GXimTriggerKey *key,
				gboolean        for_on_state)
{
	GXimServerPrivate *priv;

	g_return_if_fail (G_IS_XIM_SERVER (server));
	g_return_if_fail (key != NULL);

}
#endif
